package main.java.com.dpbird.ofbiz;


import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;

import org.apache.olingo.commons.api.data.ContextURL;
import org.apache.olingo.commons.api.data.EntityCollection;
import org.apache.olingo.commons.api.data.Parameter;
import org.apache.olingo.commons.api.data.Property;
import org.apache.olingo.commons.api.edm.EdmAction;
import org.apache.olingo.commons.api.edm.EdmEntitySet;
import org.apache.olingo.commons.api.edm.EdmEntityType;
import org.apache.olingo.commons.api.edm.EdmPrimitiveType;
import org.apache.olingo.commons.api.format.ContentType;
import org.apache.olingo.commons.api.http.HttpHeader;
import org.apache.olingo.commons.api.http.HttpStatusCode;
import org.apache.olingo.server.api.OData;
import org.apache.olingo.server.api.ODataApplicationException;
import org.apache.olingo.server.api.ODataLibraryException;
import org.apache.olingo.server.api.ODataRequest;
import org.apache.olingo.server.api.ODataResponse;
import org.apache.olingo.server.api.ServiceMetadata;
import org.apache.olingo.server.api.deserializer.ODataDeserializer;
import org.apache.olingo.server.api.prefer.Preferences;
import org.apache.olingo.server.api.prefer.PreferencesApplied;
import org.apache.olingo.server.api.processor.ActionEntityCollectionProcessor;
import org.apache.olingo.server.api.processor.ActionEntityProcessor;
import org.apache.olingo.server.api.processor.ActionPrimitiveProcessor;
import org.apache.olingo.server.api.processor.ActionVoidProcessor;
import org.apache.olingo.server.api.serializer.*;
import org.apache.olingo.server.api.uri.*;

import org.apache.ofbiz.base.util.Debug;
import org.apache.ofbiz.entity.Delegator;
import org.apache.ofbiz.entity.GenericEntityException;
import org.apache.ofbiz.service.GenericServiceException;
import org.apache.ofbiz.service.LocalDispatcher;

public class OfbizActionProcessor implements ActionVoidProcessor,ActionEntityCollectionProcessor,
		ActionEntityProcessor,ActionPrimitiveProcessor {

	public static final String module = OfbizActionProcessor.class.getName();
	private OData odata;
	private ServiceMetadata serviceMetadata;
	private LocalDispatcher dispatcher;
	private Delegator delegator;
	private Storage storage;

	public OfbizActionProcessor(Delegator delegator, LocalDispatcher dispatcher, Storage storage) {
		super();
		this.delegator = delegator;
		this.dispatcher = dispatcher;
		this.storage = storage;
	}

	@Override
	public void init(OData odata, ServiceMetadata serviceMetatdata) {
		this.odata = odata;
		this.serviceMetadata = serviceMetadata;
	}


	@Override
	public void processActionVoid(ODataRequest request, ODataResponse response, UriInfo uriInfo, ContentType requestFormat)
			throws ODataApplicationException, ODataLibraryException {
		Debug.logInfo("------------------------------------------------------------ in readFunctionImportInternal", module);

		// 1st Get the action from the resource path
		final EdmAction edmAction = ((UriResourceAction) uriInfo.asUriInfoResource().getUriResourceParts()
				.get(0)).getAction();
		final UriResourceAction uriResourceAction = (UriResourceAction) uriInfo.asUriInfoResource().getUriResourceParts().get(0);

		// 2nd Deserialize the parameter
		// In our case there is only one action. So we can be sure that parameter "Amount" has been provided by the client
		if (requestFormat == null) {
			throw new ODataApplicationException("The content type has not been set in the request.",
					HttpStatusCode.BAD_REQUEST.getStatusCode(), Locale.ROOT);
		}

		final ODataDeserializer deserializer = odata.createDeserializer(requestFormat);
		final Map<String, Parameter> actionParameters = deserializer.actionParameters(request.getBody(), edmAction)
				.getActionParameters();
		Debug.logInfo("-------------------------- edmAction.getName() = " + edmAction.getName(), module);
		// final Parameter parameterAmount = actionParameter.get(DemoEdmProvider.PARAMETER_AMOUNT);

		try {
			storage.processActionVoid(uriResourceAction, actionParameters);
		} catch (GenericServiceException e) {
			e.printStackTrace();
		} catch (GenericEntityException e) {
			e.printStackTrace();
		}

		response.setStatusCode(HttpStatusCode.NO_CONTENT.getStatusCode());

	}


	/**
	 * TODO 待实现 processActionEntityCollection
	 * @param request
	 * @param response
	 * @param uriInfo
	 * @param requestFormat
	 * @param responseFormat
	 * @throws ODataApplicationException
	 * @throws ODataLibraryException
	 */
	@Override
	public void processActionEntityCollection(ODataRequest request, ODataResponse response, UriInfo uriInfo,
											  ContentType requestFormat, ContentType responseFormat) throws ODataApplicationException, ODataLibraryException {

		Map<String, Parameter> parameters = new HashMap<String, Parameter>();
		EdmAction action = null;
		EntityCollection collection = null;

		if (requestFormat == null) {
			throw new ODataApplicationException("The content type has not been set in the request.",
					HttpStatusCode.BAD_REQUEST.getStatusCode(), Locale.ROOT);
		}

		List<UriResource> resourcePaths = uriInfo.asUriInfoResource().getUriResourceParts();
		final ODataDeserializer deserializer = odata.createDeserializer(requestFormat);
		UriResourceEntitySet boundEntitySet = (UriResourceEntitySet) resourcePaths.get(0);
		if (resourcePaths.size() > 1) {
			// Check if there is a navigation segment added after the bound parameter
			if (resourcePaths.get(1) instanceof UriResourceAction) {
				action = ((UriResourceAction) resourcePaths.get(2))
						.getAction();
				throw new ODataApplicationException("Action " + action.getName() + " is not yet implemented.",
						HttpStatusCode.NOT_IMPLEMENTED.getStatusCode(), Locale.ENGLISH);      } else {
				action = ((UriResourceAction) resourcePaths.get(1))
						.getAction();
				parameters = deserializer.actionParameters(request.getBody(), action)
						.getActionParameters();
//				collection = storage.processBoundActionEntityCollection(action, parameters);
			}
		}
		// Collections must never be null.
		// Not nullable return types must not contain a null value.
		if (collection == null
				|| collection.getEntities().contains(null) && !action.getReturnType().isNullable()) {
			throw new ODataApplicationException("The action could not be executed.",
					HttpStatusCode.INTERNAL_SERVER_ERROR.getStatusCode(), Locale.ROOT);
		}

		final Preferences.Return returnPreference = odata.createPreferences(request.getHeaders(HttpHeader.PREFER)).getReturn();
		if (returnPreference == null || returnPreference == Preferences.Return.REPRESENTATION) {
			final EdmEntitySet edmEntitySet = boundEntitySet.getEntitySet();
			final EdmEntityType type = (EdmEntityType) action.getReturnType().getType();
			final EntityCollectionSerializerOptions options = EntityCollectionSerializerOptions.with()
					.contextURL(isODataMetadataNone(responseFormat) ? null : getContextUrl(action.getReturnedEntitySet(edmEntitySet), type, false))
					.build();
			response.setContent(odata.createSerializer(responseFormat)
					.entityCollection(serviceMetadata, type, collection, options).getContent());
			response.setHeader(HttpHeader.CONTENT_TYPE, responseFormat.toContentTypeString());
			response.setStatusCode(HttpStatusCode.OK.getStatusCode());
		} else {
			response.setStatusCode(HttpStatusCode.NO_CONTENT.getStatusCode());
		}
		if (returnPreference != null) {
			response.setHeader(HttpHeader.PREFERENCE_APPLIED,
					PreferencesApplied.with().returnRepresentation(returnPreference).build().toValueString());
		}
	}

	//This method fetches the context URL
	private ContextURL getContextUrl(final EdmEntitySet entitySet, final EdmEntityType entityType,
									 final boolean isSingleEntity) throws ODataLibraryException {
		ContextURL.Builder builder = ContextURL.with();
		builder = entitySet == null ?
				isSingleEntity ? builder.type(entityType) : builder.asCollection().type(entityType) :
				builder.entitySet(entitySet);
		builder = builder.suffix(isSingleEntity && entitySet != null ? ContextURL.Suffix.ENTITY : null);
		return builder.build();
	}

	protected boolean isODataMetadataNone(final ContentType contentType) {
		return contentType.isCompatible(ContentType.APPLICATION_JSON)
				&& ContentType.VALUE_ODATA_METADATA_NONE.equalsIgnoreCase(
				contentType.getParameter(ContentType.PARAMETER_ODATA_METADATA));
	}


	/**
	 * Bound Actions will have the first segment in the resource path to be an entity set.
	 * @param request
	 * @param response
	 * @param uriInfo
	 * @param requestFormat
	 * @param responseFormat
	 * @throws ODataApplicationException
	 * @throws ODataLibraryException
	 */
	@Override
	public void processActionEntity(ODataRequest request, ODataResponse response, UriInfo uriInfo,
									ContentType requestFormat, ContentType responseFormat) throws ODataApplicationException, ODataLibraryException {

		EdmAction action = null;
		Map<String, Parameter> parameters = new HashMap<String, Parameter>();

		// OFbizEntityActionResult is a custom object
		// that holds the entity and the status as to whether the entity is created or just returned.
		// This information is used to set the response status

		OFbizEntityActionResult entityResult = null;

		if (requestFormat == null) {
			throw new ODataApplicationException("The content type has not been set in the request.",
					HttpStatusCode.BAD_REQUEST.getStatusCode(), Locale.ROOT);
		}

		final ODataDeserializer deserializer = odata.createDeserializer(requestFormat);
		final List<UriResource> resourcePaths = uriInfo.asUriInfoResource().getUriResourceParts();
		UriResourceEntitySet boundEntity = (UriResourceEntitySet) resourcePaths.get(0);

		if (resourcePaths.size() > 1) {
			if (resourcePaths.get(1) instanceof UriResourceAction) {
				action = ((UriResourceAction) resourcePaths.get(1))
						.getAction();
				parameters = deserializer.actionParameters(request.getBody(), action)
						.getActionParameters();
				try{
					entityResult =	storage.processBoundActionEntity(((UriResourceAction) resourcePaths.get(1)), parameters, boundEntity);
				} catch (GenericServiceException e) {
					e.printStackTrace();
				} catch (GenericEntityException e) {
					e.printStackTrace();
				}
//				throw new ODataApplicationException("Action " + action.getName() + " is not yet implemented.",
//						HttpStatusCode.NOT_IMPLEMENTED.getStatusCode(), Locale.ENGLISH);
			}

		}
		final EdmEntitySet edmEntitySet = boundEntity.getEntitySet();
		final EdmEntityType type = (EdmEntityType) action.getReturnType().getType();

		// Return Result...
		if (entityResult == null || entityResult.getEntity() == null) {
			if (action.getReturnType().isNullable()) {
				response.setStatusCode(HttpStatusCode.NO_CONTENT.getStatusCode());
			} else {
				// Not nullable return type so we have to give back a 500
				throw new ODataApplicationException("The action could not be executed.",
						HttpStatusCode.INTERNAL_SERVER_ERROR.getStatusCode(), Locale.ROOT);
			}
		} else {
			final Preferences.Return returnPreference = odata.createPreferences(request.getHeaders(HttpHeader.PREFER)).getReturn();
			if (returnPreference == null || returnPreference == Preferences.Return.REPRESENTATION) {
				response.setContent(odata.createSerializer(responseFormat).entity(
						serviceMetadata,
						type,
						entityResult.getEntity(),
						EntitySerializerOptions.with()
								.contextURL(isODataMetadataNone(responseFormat) ? null : getContextUrl(action.getReturnedEntitySet(edmEntitySet), type, true))
								.build())
						.getContent());
				response.setHeader(HttpHeader.CONTENT_TYPE, responseFormat.toContentTypeString());
				response.setStatusCode((entityResult.isCreated() ? HttpStatusCode.CREATED : HttpStatusCode.OK)
						.getStatusCode());
			} else {
				response.setStatusCode(HttpStatusCode.NO_CONTENT.getStatusCode());
			}
			if (returnPreference != null) {
				response.setHeader(HttpHeader.PREFERENCE_APPLIED,
						PreferencesApplied.with().returnRepresentation(returnPreference).build().toValueString());
			}
			if (entityResult.isCreated()) {
				final String location = request.getRawBaseUri() + '/'
						+ odata.createUriHelper().buildCanonicalURL(edmEntitySet, entityResult.getEntity());
				response.setHeader(HttpHeader.LOCATION, location);
				if (returnPreference == Preferences.Return.MINIMAL) {
					response.setHeader(HttpHeader.ODATA_ENTITY_ID, location);
				}
			}
			if (entityResult.getEntity().getETag() != null) {
				response.setHeader(HttpHeader.ETAG, entityResult.getEntity().getETag());
			}
		}
	}


	/**
	 * 对于有ReturnType的Action
	 * @date 2019-1-4
	 * @param oDataRequest
	 * @param oDataResponse
	 * @param uriInfo
	 * @param contentType
	 * @param contentType1
	 * @throws ODataApplicationException
	 * @throws ODataLibraryException
	 */
	@Override
	public void processActionPrimitive(ODataRequest oDataRequest,
									   ODataResponse oDataResponse, UriInfo uriInfo,
									   ContentType contentType, ContentType contentType1) throws ODataApplicationException, ODataLibraryException {

		Debug.logInfo("------------------------------------------------------------ in readFunctionImportInternal", module);

		// 1st Get the action from the resource path
		final EdmAction edmAction = ((UriResourceAction) uriInfo.asUriInfoResource().getUriResourceParts()
				.get(0)).getAction();
		final UriResourceAction uriResourceAction = (UriResourceAction) uriInfo.asUriInfoResource().getUriResourceParts().get(0);

		// 2nd Deserialize the parameter
		// In our case there is only one action. So we can be sure that parameter "Amount" has been provided by the client
		if (contentType == null) {
			throw new ODataApplicationException("The content type has not been set in the request.",
					HttpStatusCode.BAD_REQUEST.getStatusCode(), Locale.ROOT);
		}

		final ODataDeserializer deserializer = odata.createDeserializer(contentType);
		final Map<String, Parameter> actionParameters = deserializer.actionParameters(oDataRequest.getBody(), edmAction)
				.getActionParameters();
		Debug.logInfo("-------------------------- edmAction.getName() = " + edmAction.getName(), module);
		// final Parameter parameterAmount = actionParameter.get(DemoEdmProvider.PARAMETER_AMOUNT);

		Property property = null;
		try {
			property = storage.readActionImportPrimitive(uriResourceAction, actionParameters);
		} catch (GenericServiceException e) {
			e.printStackTrace();
		} catch (GenericEntityException e) {
			e.printStackTrace();
		}

		if (property == null) {
			throw new ODataApplicationException("Nothing found.", HttpStatusCode.NOT_FOUND.getStatusCode(),
					Locale.ROOT);
		}

		// 2nd step: Serialize the response entity
		final EdmPrimitiveType edmPrimitiveType = (EdmPrimitiveType) uriResourceAction.getAction().getReturnType().getType();
		final ContextURL contextURL = ContextURL.with().type(edmPrimitiveType).build();
		final PrimitiveSerializerOptions opts = PrimitiveSerializerOptions.with().contextURL(contextURL).build();
		final ODataSerializer serializer = odata.createSerializer(contentType);
		final SerializerResult serializerResult = serializer.primitive(serviceMetadata, edmPrimitiveType, property, opts);

		// 3rd configure the response object
		oDataResponse.setContent(serializerResult.getContent());
		oDataResponse.setStatusCode(HttpStatusCode.OK.getStatusCode());
		oDataResponse.setHeader(HttpHeader.CONTENT_TYPE, contentType.toContentTypeString());
	}
}